package com.yao2san.simdif.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.yao2san.simdif.bean.request.DbDiffReq;
import com.yao2san.simdif.entity.*;
import com.yao2san.simdif.enums.ColumnAttr;
import com.yao2san.simdif.enums.DatasourceAttr;
import com.yao2san.simdif.enums.DiffType;
import com.yao2san.simdif.enums.TableAttr;
import com.yao2san.simdif.service.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author wxg
 */
@Service
@Slf4j
public class DbDifServiceImpl implements DbDifService {
    private final DatasourceManageService datasourceManageService;

    private final TableInfoService tableInfoService;
    private final HistoryService historyService;

    private ThreadLocal<CompareConfig> currCompareConfig = new ThreadLocal<>();

    public DbDifServiceImpl(DatasourceManageService datasourceManageService, TableInfoService tableInfoService, HistoryService historyService) {
        this.datasourceManageService = datasourceManageService;
        this.tableInfoService = tableInfoService;
        this.historyService = historyService;
    }


    @Override
    public Result dif(DbDiffReq req) {
        Long ds1 = req.getDatasourceId1();
        Long ds2 = req.getDatasourceId2();
        CompareConfig compareConfig = Optional.ofNullable(req.getCompareConfig()).orElse(new CompareConfig());
        currCompareConfig.set(compareConfig);
        //通过id加载数据源配置
        Datasource datasource1 = datasourceManageService.getDatasource(ds1);
        Datasource datasource2 = datasourceManageService.getDatasource(ds2);

        LocalDateTime now = LocalDateTime.now();
        updateLastCompareTime(ds1, now);
        updateLastCompareTime(ds2, now);
        Result result = calc(datasource1, datasource2);
        saveHistory(datasource1, datasource2, result);
        return result;
    }

    private void saveHistory(Datasource datasource1, Datasource datasource2, Result result) {
        Result.DatasourceSummary summary = result.getSummary();

        History history = new History();
        history.setDiffCount(summary.getDiffCount());
        history.setIsDiff(summary.getDiffCount() > 0);
        history.setDs1Id(datasource1.getId());
        history.setDs2Id(datasource2.getId());
        history.setCompareTime(LocalDateTime.now());
        historyService.save(history);
    }

    private Result calc(Datasource d1, Datasource d2) {
        Result result = new Result();

        //数据源差异结果
        Result.DatasourceDiff datasourceDiff = calcDatasourceDiff(d1, d2);
        result.setDatasourceDiff(datasourceDiff);

        //表差异结果
        List<Result.TableDiff> tableDiffs = buildTableDiff(d1.getId(), d2.getId());
        result.setTableDiffs(tableDiffs);

        Result.DatasourceSummary summary = buildSummary(datasourceDiff, tableDiffs);
        result.setSummary(summary);
        return result;
    }


    private Result.DatasourceSummary buildSummary(Result.DatasourceDiff datasourceDiff, List<Result.TableDiff> tableDiffs) {
        Result.DatasourceSummary summary = new Result.DatasourceSummary();
        summary.setLeftTableCount(datasourceDiff.getLeft().getTableCount());
        summary.setRightTableCount(datasourceDiff.getRight().getTableCount());
        summary.setDiffCount(tableDiffs.stream().mapToInt(Result.TableDiff::getDiffCount).sum());
        int lackTableCount = 0;
        int diffTableCount = 0;
        int leftColumnLackCount = 0;
        int leftColumnDiffCount = 0;
        int rightColumnLackCount = 0;
        int rightColumnDiffCount = 0;
        for (Result.TableDiff tableDiff : tableDiffs) {
            if (tableDiff.getDiffType() == DiffType.LACK) {
                lackTableCount++;
            }
            if (tableDiff.getDiffType() == DiffType.DIFF) {
                diffTableCount++;
            }
            List<Result.ColumnDiff> leftDiffs = tableDiff.getLeft().getColumnDiffs();
            List<Result.ColumnDiff> rightDiffs = tableDiff.getRight().getColumnDiffs();
            leftColumnLackCount += (int) leftDiffs.stream().filter(v -> v.getDiffType() == DiffType.LACK && v.getLeft()!=null && v.getLeft().getColumnName()==null).count();
            rightColumnLackCount += (int) rightDiffs.stream().filter(v -> v.getDiffType() == DiffType.LACK && v.getRight()!=null && v.getRight().getColumnName()==null).count();
            leftColumnDiffCount += leftDiffs.stream().filter(v -> v.getDiffType() == DiffType.DIFF).mapToInt(Result.ColumnDiff::getDiffCount).sum();
            rightColumnDiffCount += rightDiffs.stream().filter(v -> v.getDiffType() == DiffType.DIFF).mapToInt(Result.ColumnDiff::getDiffCount).sum();
        }
        summary.setLackTableCount(lackTableCount);
        summary.setDiffTableCount(diffTableCount);
        summary.setLeftColumnLackCount(leftColumnLackCount);
        summary.setLeftColumnDiffCount(leftColumnDiffCount);
        summary.setRightColumnLackCount(rightColumnLackCount);
        summary.setRightColumnDiffCount(rightColumnDiffCount);
        return summary;
    }

    private List<Result.TableDiff> buildTableDiff(Long datasourceId1, Long datasourceId2) {
        List<Result.TableDiff> tableDiffs = new ArrayList<>();

        //获取表信息
        List<TableInfo> tables1 = datasourceManageService.getTableInfos(datasourceId1);
        List<TableInfo> tables2 = datasourceManageService.getTableInfos(datasourceId2);

        //获取列信息
        List<ColumnInfo> columnInfos1 = datasourceManageService.getColumnInfos(datasourceId1);
        List<ColumnInfo> columnInfos2 = datasourceManageService.getColumnInfos(datasourceId2);

        //对列 按表名称分组
        Map<String, List<ColumnInfo>> group1 = columnInfos1.stream().collect(Collectors.groupingBy(ColumnInfo::getTableName));
        Map<String, List<ColumnInfo>> group2 = columnInfos2.stream().collect(Collectors.groupingBy(ColumnInfo::getTableName));


        Map<String, TableInfo> m1 = new HashMap<>();
        Map<String, TableInfo> m2 = new HashMap<>();

        for (TableInfo tableInfo : tables1) {
            m1.put(tableInfo.getTableName(), tableInfo);
        }
        for (TableInfo tableInfo : tables2) {
            m2.put(tableInfo.getTableName(), tableInfo);
        }
        //左边有 右边没有的
        List<TableInfo> tableInLeft = new ArrayList<>();
        //右边有 左边边没有的
        List<TableInfo> tableInRight = new ArrayList<>();
        //两边都有的
        List<TableInfo> tableBothLeft = new ArrayList<>();
        List<TableInfo> tableBothRight = new ArrayList<>();
        for (TableInfo tableInfo : tables1) {
            if (!m2.containsKey(tableInfo.getTableName())) {
                tableInLeft.add(tableInfo);
            } else {
                tableBothLeft.add(tableInfo);
            }
        }

        for (TableInfo tableInfo : tables2) {
            if (!m1.containsKey(tableInfo.getTableName())) {
                tableInRight.add(tableInfo);
            } else {
                tableBothRight.add(tableInfo);
            }
        }

        tableBothLeft.sort(Comparator.comparing(TableInfo::getTableName));
        tableBothRight.sort(Comparator.comparing(TableInfo::getTableName));


        //两边都有的表
        for (int i = 0; i < tableBothLeft.size(); i++) {
            Result.TableDiff tableDiff = calcTableDiff(tableBothLeft.get(i), tableBothRight.get(i));


            List<Result.ColumnDiff> columnDiffs = new ArrayList<>();


            String tableName = tableBothLeft.get(i).getTableName();
            List<ColumnInfo> cols1 = group1.get(tableName);
            List<ColumnInfo> cols2 = group2.get(tableName);

            cols1.sort(Comparator.comparing(ColumnInfo::getColumnSort));
            cols2.sort(Comparator.comparing(ColumnInfo::getColumnSort));

            Map<String, ColumnInfo> mc1 = new HashMap<>();
            Map<String, ColumnInfo> mc2 = new HashMap<>();

            for (ColumnInfo columnInfo : cols1) {
                mc1.put(columnInfo.getColumnName(), columnInfo);
            }
            for (ColumnInfo columnInfo : cols2) {
                mc2.put(columnInfo.getColumnName(), columnInfo);
            }

            //左边有 右边没有的列
            List<ColumnInfo> columnInLeft = new ArrayList<>();
            //右边有 左边边没有的列
            List<ColumnInfo> columnInRight = new ArrayList<>();
            //两边都有的列
            List<ColumnInfo> columnBothLeft = new ArrayList<>();
            List<ColumnInfo> columnBothRight = new ArrayList<>();

            for (ColumnInfo columnInfo : cols1) {
                if (!mc2.containsKey(columnInfo.getColumnName())) {
                    columnInLeft.add(columnInfo);
                } else {
                    columnBothLeft.add(columnInfo);
                }
            }

            for (ColumnInfo columnInfo : cols2) {
                if (!mc1.containsKey(columnInfo.getColumnName())) {
                    columnInRight.add(columnInfo);
                } else {
                    columnBothRight.add(columnInfo);
                }
            }

            Map<String, ColumnInfo> leftColumnMapping = new HashMap<>();
            Map<String, ColumnInfo> rightColumnMapping = new HashMap<>();
            for (ColumnInfo columnInfo : columnBothLeft) {
                leftColumnMapping.put(columnInfo.getColumnName(), columnInfo);
            }
            for (ColumnInfo columnInfo : columnBothRight) {
                rightColumnMapping.put(columnInfo.getColumnName(), columnInfo);
            }
            //两边都有的列
            for (ColumnInfo columnInfo : columnBothLeft) {
                String columnName = columnInfo.getColumnName();
                Result.ColumnDiff columnDiff = calcColumnDiff(leftColumnMapping.get(columnName), rightColumnMapping.get(columnName));
                if (currCompareConfig.get().getOnlyDiff()) {
                    if (columnDiff.getDiffType() != DiffType.SAME) {
                        columnDiffs.add(columnDiff);
                    }
                } else {
                    columnDiffs.add(columnDiff);
                }
            }
            //仅左边存在的列
            for (ColumnInfo columnInfo : columnInLeft) {
                columnDiffs.add(calcColumnDiff(columnInfo, null));
            }
            //仅右边存在的列
            for (ColumnInfo columnInfo : columnInRight) {
                columnDiffs.add(calcColumnDiff(null, columnInfo));
            }

            //表的差异计数(表差异数+字段差异数)
            if (columnDiffs.stream().anyMatch(v -> v.getDiffType() != DiffType.SAME)) {
                tableDiff.setDiffType(DiffType.DIFF);
                int columnDiffCount = columnDiffs.stream().mapToInt(Result.ColumnDiff::getDiffCount).sum();
                int tableDiffCount = tableDiff.getDiffCount();
                tableDiff.setDiffCount((columnDiffCount + tableDiffCount));
            }

            tableDiff.getLeft().setColumnAttrs(columnDiffs.stream().map(Result.ColumnDiff::getLeft).collect(Collectors.toList()));
            tableDiff.getRight().setColumnAttrs(columnDiffs.stream().map(Result.ColumnDiff::getRight).collect(Collectors.toList()));

            tableDiff.getLeft().setColumnDiffs(columnDiffs);
            tableDiff.getRight().setColumnDiffs(columnDiffs);

            //是否仅看有差异的
            if (currCompareConfig.get().getOnlyDiff()) {
                if (tableDiff.getDiffType() == DiffType.SAME && tableDiff.getDiffCount() == 0) {
                    continue;
                }
            }
            tableDiffs.add(tableDiff);

        }
        //仅左边的表
        for (TableInfo tableInfo : tableInLeft) {
            Result.TableDiff tableDiff = calcTableDiff(tableInfo, null);

            String tableName = tableInfo.getTableName();
            List<ColumnInfo> columnInLeft = group1.get(tableName);
            List<Result.ColumnDiff> columnDiffs = new ArrayList<>();
            for (ColumnInfo columnInfo : columnInLeft) {
                columnDiffs.add(calcColumnDiff(columnInfo, null));
            }

            tableDiff.getLeft().setColumnAttrs(columnDiffs.stream().map(Result.ColumnDiff::getLeft).collect(Collectors.toList()));
            tableDiffs.add(tableDiff);

            tableDiff.getLeft().setColumnDiffs(new ArrayList<>());
            tableDiff.getRight().setColumnDiffs(new ArrayList<>());

        }
        //仅右边的表
        for (TableInfo tableInfo : tableInRight) {
            Result.TableDiff tableDiff = calcTableDiff(null, tableInfo);

            String tableName = tableInfo.getTableName();
            List<ColumnInfo> columnInRight = group2.get(tableName);
            List<Result.ColumnDiff> columnDiffs = new ArrayList<>();
            for (ColumnInfo columnInfo : columnInRight) {
                columnDiffs.add(calcColumnDiff(null, columnInfo));
            }
            tableDiff.getRight().setColumnAttrs(columnDiffs.stream().map(Result.ColumnDiff::getRight).collect(Collectors.toList()));
            tableDiffs.add(tableDiff);

            tableDiff.getLeft().setColumnDiffs(new ArrayList<>());
            tableDiff.getRight().setColumnDiffs(new ArrayList<>());
        }
        return tableDiffs;
    }


    private Result.DatasourceDiff calcDatasourceDiff(Datasource d1, Datasource d2) {
        Result.DatasourceAttr da1 = new Result.DatasourceAttr();
        Result.DatasourceAttr da2 = new Result.DatasourceAttr();
        LinkedHashSet<Result.DiffInstance> diff = new LinkedHashSet<>();
        Long countedTable1 = countTable(d1.getId());
        Long countedTable2 = countTable(d2.getId());
        diff.add(new Result.DiffInstance(DatasourceAttr.TABLE_COUNT.getAttrCode(), countedTable1, countedTable2, getDiffType(countedTable1, countedTable2)));
        diff.add(new Result.DiffInstance(DatasourceAttr.FUNCTION_COUNT.getAttrCode(), 0, 0, getDiffType(0, 0)));
        diff.add(new Result.DiffInstance(DatasourceAttr.PRODUCE_COUNT.getAttrCode(), 0, 0, getDiffType(0, 0)));

        da1.setDiff(diff);
        da2.setDiff(diff);
        da1.setName(d1.getName());
        da2.setName(d2.getName());
        da1.setDatabase(d1.getDatabase());
        da2.setDatabase(d2.getDatabase());
        da1.setTableCount(d1.getTableCount());
        da2.setTableCount(d2.getTableCount());
        Result.DatasourceDiff datasourceDiff = new Result.DatasourceDiff();
        datasourceDiff.setLeft(da1);
        datasourceDiff.setRight(da2);

        return datasourceDiff;
    }

    private Long countTable(Long datasourceId) {
        LambdaQueryWrapper<TableInfo> query = Wrappers.lambdaQuery();
        query.eq(TableInfo::getDatasourceId, datasourceId);
        return tableInfoService.count(query);
    }

    private Result.TableDiff calcTableDiff(TableInfo t1, TableInfo t2) {
        Result.TableDiff tableDiff = new Result.TableDiff();

        Result.TableAttr ta1 = new Result.TableAttr();
        Result.TableAttr ta2 = new Result.TableAttr();
        if (t1 == null) {
            ta1.setDiffType(DiffType.LACK);
            tableDiff.setDiffType(DiffType.LACK);
        } else {
            BeanUtils.copyProperties(t1, ta1);
        }
        if (t2 == null) {
            ta2.setDiffType(DiffType.LACK);
            tableDiff.setDiffType(DiffType.LACK);
        } else {
            BeanUtils.copyProperties(t2, ta2);
        }

        LinkedHashSet<Result.DiffInstance> diff = new LinkedHashSet<>();
        diff.add(new Result.DiffInstance(TableAttr.TABLE_NAME.getAttrCode(), ta1.getTableName(), ta2.getTableName(), getDiffType(ta1.getTableName(), ta2.getTableName())));
        diff.add(new Result.DiffInstance(TableAttr.TABLE_TYPE.getAttrCode(), ta1.getTableType(), ta2.getTableType(), getDiffType(ta1.getTableType(), ta2.getTableType())));
        diff.add(new Result.DiffInstance(TableAttr.TABLE_ENGINE.getAttrCode(), ta1.getTableEngine(), ta2.getTableEngine(), getDiffType(ta1.getTableEngine(), ta2.getTableEngine())));
        diff.add(new Result.DiffInstance(TableAttr.TABLE_ROWS.getAttrCode(), ta1.getTableRows(), ta2.getTableRows(), getDiffType(ta1.getTableRows(), ta2.getTableRows())));
        diff.add(new Result.DiffInstance(TableAttr.TABLE_INDEX.getAttrCode(), ta1.getTableIndex(), ta2.getTableIndex(), getDiffType(ta1.getTableIndex(), ta2.getTableIndex())));
        if (!currCompareConfig.get().getIgnoreTableComment()) {
            diff.add(new Result.DiffInstance(TableAttr.TABLE_COMMENT.getAttrCode(), ta1.getTableComment(), ta2.getTableComment(), getDiffType(ta1.getTableComment(), ta2.getTableComment())));
        }
        ta1.setDiff(diff);
        ta2.setDiff(diff);

        if (tableDiff.getDiffType() != DiffType.LACK) {
            if (diff.stream().allMatch(v -> v.getDiffType() == DiffType.SAME)) {
                ta1.setDiffType(DiffType.SAME);
                ta2.setDiffType(DiffType.SAME);
                tableDiff.setDiffType(DiffType.SAME);
            }
            if (diff.stream().anyMatch(v -> v.getDiffType() != DiffType.SAME)) {
                ta1.setDiffType(DiffType.DIFF);
                ta2.setDiffType(DiffType.DIFF);
                tableDiff.setDiffType(DiffType.DIFF);
                tableDiff.setDiffCount((int) diff.stream().filter(v -> v.getDiffType() != DiffType.SAME).count());
            }
        }
        tableDiff.setLeft(ta1);
        tableDiff.setRight(ta2);

        return tableDiff;
    }

    private Result.ColumnDiff calcColumnDiff(ColumnInfo c1, ColumnInfo c2) {
        Result.ColumnDiff columnDiff = new Result.ColumnDiff();

        Result.ColumnAttr ca1 = new Result.ColumnAttr();
        Result.ColumnAttr ca2 = new Result.ColumnAttr();
        if (c1 == null) {
            ca1.setDiffType(DiffType.LACK);
            columnDiff.setDiffType(DiffType.LACK);
            columnDiff.setDiffCount(1);
        } else {
            BeanUtils.copyProperties(c1, ca1);
        }
        if (c2 == null) {
            ca2.setDiffType(DiffType.LACK);
            columnDiff.setDiffType(DiffType.LACK);
            columnDiff.setDiffCount(1);
        } else {
            BeanUtils.copyProperties(c2, ca2);
        }

        LinkedHashSet<Result.DiffInstance> diff = new LinkedHashSet<>();
        diff.add(new Result.DiffInstance(ColumnAttr.COLUMN_NAME.getAttrCode(), ca1.getColumnName(), ca2.getColumnName(), getDiffType(ca1.getColumnName(), ca2.getColumnName())));
        diff.add(new Result.DiffInstance(ColumnAttr.COLUMN_TYPE.getAttrCode(), ca1.getColumnType(), ca2.getColumnType(), getDiffType(ca1.getColumnType(), ca2.getColumnType())));
        diff.add(new Result.DiffInstance(ColumnAttr.COLUMN_DEFAULT.getAttrCode(), ca1.getColumnDefault(), ca2.getColumnDefault(), getDiffType(ca1.getColumnDefault(), ca2.getColumnDefault())));
        //diff.add(new Result.DiffInstance(ColumnAttr.COLUMN_INDEX.getAttrCode(), ca1.getColumnIndex(), ca2.getColumnIndex(), getDiffType(ca1.getColumnIndex(), ca2.getColumnIndex())));
        //diff.add(new Result.DiffInstance(ColumnAttr.COLUMN_SORT.getAttrCode(), ca1.getColumnSort(), ca2.getColumnSort(), getDiffType(ca1.getColumnSort(), ca2.getColumnSort())));
        //diff.add(new Result.DiffInstance(ColumnAttr.COLUMN_DATA_TYPE.getAttrCode(), ca1.getColumnDataType(), ca2.getColumnDataType(), getDiffType(ca1.getColumnDataType(), ca2.getColumnDataType())));
        //diff.add(new Result.DiffInstance(ColumnAttr.COLUMN_LENGTH.getAttrCode(), ca1.getColumnLength(), ca2.getColumnLength(), getDiffType(ca1.getColumnLength(), ca2.getColumnLength())));
        if (!currCompareConfig.get().getIgnoreColumnNullable()) {
            diff.add(new Result.DiffInstance(ColumnAttr.COLUMN_NULL.getAttrCode(), ca1.getColumnNull(), ca2.getColumnNull(), getDiffType(ca1.getColumnNull(), ca2.getColumnNull())));
        }
        if (!currCompareConfig.get().getIgnoreColumnComment()) {
            diff.add(new Result.DiffInstance(ColumnAttr.COLUMN_COMMENT.getAttrCode(), ca1.getColumnComment(), ca2.getColumnComment(), getDiffType(ca1.getColumnComment(), ca2.getColumnComment())));
        }
        ca1.setDiff(diff);
        ca2.setDiff(diff);


        if (columnDiff.getDiffType() != DiffType.LACK) {
            if (diff.stream().allMatch(v -> v.getDiffType() == DiffType.SAME)) {
                ca1.setDiffType(DiffType.SAME);
                ca2.setDiffType(DiffType.SAME);
                columnDiff.setDiffType(DiffType.SAME);
            }
            if (diff.stream().anyMatch(v -> v.getDiffType() != DiffType.SAME)) {
                ca1.setDiffType(DiffType.DIFF);
                ca2.setDiffType(DiffType.DIFF);
                columnDiff.setDiffType(DiffType.DIFF);
                columnDiff.setDiffCount((int) diff.stream().filter(v -> v.getDiffType() != DiffType.SAME).count());
            }
        }

        columnDiff.setLeft(ca1);
        columnDiff.setRight(ca2);

        return columnDiff;
    }

    private DiffType getDiffType(Object o1, Object o2) {
        //其中一个为空
        if (Objects.isNull(o1) || Objects.isNull(o2)) {
            //如果都为空则按相同处理
            if (Objects.isNull(o1) && Objects.isNull(o2)) {
                return DiffType.SAME;
            }
            return DiffType.LACK;
        }
        if (!Objects.equals(o1, o2)) {
            return DiffType.DIFF;
        }
        return DiffType.SAME;
    }

    @Override
    public void getReport(Long id) {

    }

    private void updateLastCompareTime(Long datasourceId, LocalDateTime time) {
        datasourceManageService.updateById(new Datasource()
                .setId(datasourceId)
                .setLastCompareTime(time));
    }
}
